﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Web;
using System.Web.Mvc;
using Mindfor.Web.Config;
using Mindfor.Web.Data;
using Mindfor.Web.Features;
using Mindfor.Web.Models;
using Mindfor.Web.Modules.Users;
using Mindfor.Web.Routing;
using NHibernate;
using NHibernate.Linq;

namespace Mindfor.Web.Modules.Catalog.Controllers
{
	public class ProductsController : CmsController
	{

		/// <summary>
		/// Returns sub categories view
		/// </summary>
		/// <param name="page">Page number.</param>
		/// <returns>PageResult.</returns>
		[UrlRoute(1, ""), UrlRouteDefault(1, "page", 1)]
		[UrlRoute(2, "Page{page}"), UrlRouteConstraint(2, "page", Constraints.IsIntMore1)]
		//[UrlRoute(3, "Category/{parentId}"), UrlRouteDefault(3, "page", 1)]//, UrlRouteConstraint(3, "parentId", Constraints.IsInt)]
		//[UrlRoute(4, "Category/{parentId}/Page{page}"), UrlRouteConstraint(4, "page", Constraints.IsIntMore1), UrlRouteConstraint(4, "parentId", Constraints.IsInt)]
		public ActionResult Index(int page, int? parentId)
		{
			//admin key
			ViewData["IsEditable"] = User != null && User.IsInRole(CmsRoles.Administrator, CatalogModule.ProductsRole);
			ViewData["ShowTiles"] = Module.Options[CatalogModule.ShowTilesOption].GetValue<bool>();

			// add parent category to the model
			Category parent = null;
			if (parentId.HasValue)
			{
				parent = Data.Get<Category>(parentId.Value);
				if (parent == null)
					//if parentId has value but we don't have Category with this Id
					return HttpNotFound();
				ViewData["ShowTiles"] = parent.ShowTiles;
			}
			ViewData["Parent"] = parent;

			//createing query to get categories with parent
			List<Category> catList = Data.Query<Category>()
				.Where(r => r.Parent == parent)
				.OrderBy(r => r.Sort)
				.ToList();

			//if there are no categories with such parent try to load products with such parents
			if (catList.Count == 0)
			{
				//process page number and size
				int pageIndex = page - 1;
				if (pageIndex < 0)
					return HttpNotFound();
				int pageSize = Module.Options[CatalogModule.IndexProductsPageSizeOption].GetValue<int>();

				//get all products where parentCategory == parent
				IQueryable<Product> prodQuery = Data.Query<Product>().Where(r => r.Category == parent);
				ViewData.Model = prodQuery
					.Skip(pageIndex * pageSize)
					.Take(pageSize)
					.ToList();

				ViewData["Pager"] = new PagerModel(prodQuery.Count(), pageSize);
				return View("IndexProducts");
			}

			//if there are categories with such parent - put them to model
			ViewData.Model = catList;
			return View("IndexCategories");
		}

		/// <summary>
		/// Returns PageResult which renders Catalog Admin index view.
		/// </summary>
		/// <param name="page">Page number.</param>
		/// <returns>PageResult.</returns>
		[UrlRoute(1, "", Path = "Admin/Catalog")]
		[UrlRouteDefault(1, "page", 1)]
		[UrlRoute(2, "Page{page}")]
		[UrlRouteConstraint(2, "page", Constraints.IsInt)]
		[AdminLink("Каталог", "Сайт")]
		[CmsAuthorize(CatalogModule.ProductsRole)]
		public ActionResult IndexAdmin(int page)
		{
			// get categories tree and add it to model
			List<Category> all = Data.Query<Category>().ToList();
			ViewData.Model = GenerateCategoriesTree(all.Where(r => r.Parent == null), null);
			return View();
		}

		[Widget("Categories")]
		[UrlSubAction("Index")]
		public ActionResult CategoriesWidget()
		{
			List<Category> all = Data.Query<Category>().ToList();
			ViewData.Model = all.Where(r => r.Parent == null);

			// pass selected values only if came from Index
			if (ControllerContext.ParentActionViewContext != null)
			{
				Type parentControllerType = ControllerContext.ParentActionViewContext.Controller.GetType();
				string action = ControllerContext.ParentActionViewContext.RouteData.Values["action"].ToString();

				int? categoryId = null;
				if (parentControllerType == this.GetType())
				{
					if (action == "Index")
						categoryId = (int?)ControllerContext.ParentActionViewContext.Controller.ValueProvider.GetValue<int?>("parentId");
					else if (action == "Item")
					{
						int productId = (int)ControllerContext.ParentActionViewContext.Controller.ValueProvider.GetValue<int>("id");
						Product p = Data.Get<Product>(productId);
						if (p != null && p.Category != null)
							categoryId = p.Category.Id;
					}
				}

				if (categoryId.HasValue)
				{
					List<Category> expanded = new List<Category>();
					Category category = all.FirstOrDefault(r => r.Id == categoryId.Value);
					while (category != null)
					{
						expanded.Add(category);
						category = category.Parent;
					}
					ViewData["Expanded"] = expanded;
				}
			}

			return PartialView();
		}

		/// <summary>
		/// Category item creating page.
		/// </summary>
		[HttpGet]
		[UrlSubAction("IndexAdmin")]
		[UrlRoute("Categories/Add", Path = "Admin/Catalog")]
		[LoadModelState]
		[ReturnUrlEnable]
		[CmsAuthorize(CatalogModule.ProductsRole)]
		public ActionResult AddCategory(int? parentId)
		{
			return EditCategory(null, parentId);
		}

		[HttpPost]
		[ValidateInput(false)]
		public ActionResult AddCategory(FormCollection form, int? imageType, string returnUrl)
		{
			return EditCategory(null, form, imageType, returnUrl);
		}

		[HttpGet]
		[UrlSubAction("IndexAdmin")]
		[UrlRoute("Categories/{id}/Edit", Path = "Admin/Catalog")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		[CmsAuthorize(CatalogModule.ProductsRole)]
		[LoadModelState]
		[ReturnUrlEnable]
		public ActionResult EditCategory(int? id, int? parentId)
		{
			Category category = null;
			if (id.HasValue)
			{
				category = Data.Get<Category>(id.Value);
				if (category == null)
					return HttpNotFound();

				ViewData.Model = category;
				parentId = category.Parent != null ? (int?)category.Parent.Id : null;
			}
			else
			{
				ModelState.SetDefaultModelValue("ShowTiles", Module.Options[CatalogModule.ShowTilesOption].GetValue<bool>());
			}

			//dropdown list with categories
			List<Category> categories = Data.Query<Category>().ToList();
			List<SelectListItem> categoriesItems = GenerateCategoriesTree(categories.Where(r => r.Parent == null), category)
				.Select(r => new SelectListItem
				{
					Text = "---".Multiply(r.Level) + " " + r.Title,
					Value = r.Id.ToString()
				})
				.ToList();
			categoriesItems.Insert(0, new SelectListItem { Text="" });
			ViewBag.Parents = categoriesItems;

			if (ViewData["Parent.Id"] == null)
				ViewData["Parent.Id"] = parentId;

			if (parentId.HasValue)
				ViewData["Parent"] = Data.Get<Category>(parentId);

			return View("EditCategory");
		}

		/// <summary>
		/// Generates category leveled items to display tree.
		/// </summary>
		/// <param name="root">Root items.</param>
		/// <param name="exclude">Category to exclude form list.</param>
		/// <param name="level">Current deep-level.</param>
		private List<CategoryLeveled> GenerateCategoriesTree(IEnumerable<Category> root, Category exclude, int level = 0)
		{
			List<CategoryLeveled> items = new List<CategoryLeveled>();
			foreach (Category c in root.OrderBy(r => r.Sort))
			{
				if (c != exclude)
				{
					items.Add(new CategoryLeveled(c, level));
					items.AddRange(GenerateCategoriesTree(c.SubCategories, exclude, level+1));
				}
			}
			return items;
		}

		/// <summary>
		/// Creates folder for storage of images for categories and products.
		/// </summary>
		/// <returns>Folder</returns>
		private Folder CreateFolder()
		{
			Folder f = Data.Query<Folder>().FirstOrDefault(r => r.Name == Module.Name && r.Parent == null);
			if (f == null)
			{
				f = new Folder(Module.Name);
				using (ITransaction t = Data.BeginTransaction())
				{
					Data.Save(f);
					t.Commit();
				}
			}
			return f;
		}

		[HttpPost]
		[CmsAuthorize(CatalogModule.ProductsRole)]
		[ValidateInput(false)]
		public ActionResult EditCategory(int? id, FormCollection form, int? imageType, string returnUrl)
		{
			// load category
			Category category;
			if (id.HasValue)
			{
				category = Data.Get<Category>(id.Value);
				if (category == null)
					return HttpNotFound();
			}
			else
				category = new Category();

			// bind
			TryUpdateModel(category, null, null, new string[] { "Picture", "Sort" });

			// is model valid?
			if (ModelState.IsValid)
			{
				// typograf text
				category.Text = Typograf.ProcessText(category.Text, TypografEntityTypes.HtmlEntities);

				// load image
				switch (imageType)
				{
					// insert new image
					case null:
					case 0:
						if (Request.Files["Picture"] != null)
							category.Picture = Request.Files["Picture"].GetBytes();
						break;
					// delete
					case 1:
						category.Picture = null;
						break;
				};

				// if our item new and we get Sort number for it
				if (id == null)
				{
					IQueryable<Category> brothers = Data.Query<Category>().Where(r => r.Parent == category.Parent);
					category.Sort = brothers.Count() == 0 ? 0 : (brothers.Max(r => r.Sort) + 1);
				}

				// submit changes to db. Ta-daaaam!
				using (ITransaction t = Data.BeginTransaction())
				{
					Data.SaveOrUpdate(category);
					t.Commit();
				}

				if (id == null)
					return RedirectToAction("Index", new { parentId = category.Id });
				return RedirectToReturnUrl(Url.Action("Index", new { parentId = category.Id }));
			}

			this.SaveModelState();
			return RedirectToAction(id == null ? "AddCategory" : "EditCategory", new { returnUrl });
		}


		/// <summary>
		/// Delete news item.
		/// </summary>
		/// <param name="id">News item id.</param>
		[HttpGet]
		[UrlSubAction("IndexAdmin")]
		[UrlRoute("Categories/{id}/Delete", Path = "Admin/Catalog")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		[CmsAuthorize(CatalogModule.ProductsRole)]
		[ReturnUrlEnable]
		public ActionResult DeleteCategory(int id, string returnUrl)
		{
			// get news
			Category model = Data.Get<Category>(id);
			if (model == null)
				return HttpNotFound();

			if (returnUrl == null)
				returnUrl = Url.Action("IndexAdmin");

			if (model.SubCategories.Count > 0)
				return Message("Нельзя удалять категорию, содержащюю подкатегории. Сначала удалить все подкатегории.", returnUrl);
			else if (model.Products.Count > 0)
				return Message("Нельзя удалять категорию, содержащюю продукты. Сначала удалить все продукты.", returnUrl);
			else
			{
				// delete
				using (ITransaction t = Data.BeginTransaction())
				{
					Data.Delete(model);
					t.Commit();
				}
			}
			// return
			return Redirect(returnUrl);
		}

		/// <summary>
		/// Moves category up. Redirects back to IndexAdmin.
		/// </summary>
		/// <param name="id">Category id.</param>
		[UrlSubAction("IndexAdmin")]
		[UrlRoute("Categories/{id}/MoveUp", Path = "Admin/Catalog")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		public ActionResult MoveUp(int id)
		{
			// get category
			Category cat = Data.Get<Category>(id);
			if (cat == null)
				return HttpNotFound();

			// normalize
			Category catUp = null;
			List<Category> categories = Data.Query<Category>()
				.Where(r => r.Parent == cat.Parent)
				.OrderBy(r => r.Sort)
				.ToList();
			for (int i = 0; i < categories.Count; i++)
			{
				categories[i].Sort = i;
				if (i > 0 && categories[i] == cat)
					catUp = categories[i-1];
			}

			// exchange sort
			if (catUp != null)
			{
				int buf = cat.Sort;
				cat.Sort = catUp.Sort;
				catUp.Sort = buf;

				using (ITransaction t = Data.BeginTransaction())
				{
					Data.Update(catUp);
					Data.Update(cat);
					t.Commit();
				}
			}

			// redirect
			return RedirectToReturnUrl(Url.Action("IndexAdmin"));
		}

		/// <summary>
		/// Moves category down. Redirects back to IndexAdmin.
		/// </summary>
		/// <param name="id">Category id.</param>
		[UrlSubAction("IndexAdmin")]
		[UrlRoute("Categories/{id}/MoveDown", Path = "Admin/Catalog")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		public ActionResult MoveDown(int id)
		{
			// get category
			Category cat = Data.Get<Category>(id);
			if (cat == null)
				return HttpNotFound();

			// normalize
			Category catDwn = null;
			List<Category> categories = Data.Query<Category>()
				.Where(r => r.Parent == cat.Parent)
				.OrderBy(r => r.Sort)
				.ToList();

			for (int i = 0; i < categories.Count; i++)
			{
				categories[i].Sort = i;
				if (i >= 0 && categories[i] == cat && i != categories.Count-1)
					catDwn = categories[i + 1];
			}

			// exchange sort
			if (catDwn != null)
			{
				int buf = cat.Sort;
				cat.Sort = catDwn.Sort;
				catDwn.Sort = buf;

				using (ITransaction t = Data.BeginTransaction())
				{
					Data.Update(catDwn);
					Data.Update(cat);
					t.Commit();
				}
			}

			// redirect
			return RedirectToReturnUrl(Url.Action("IndexAdmin"));
		}

		[HttpGet]
		[UrlSubAction("Index")]
		[UrlRoute("{id}")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		[ReturnUrlEnable]
		public ActionResult Item(int id)
		{
			//check if we have the item
			Product item = Data.Get<Product>(id);
			if (item == null)
				return HttpNotFound();

			//admin key
			ViewData["IsEditable"] = User != null && User.IsInRole(CmsRoles.Administrator, CatalogModule.ProductsRole);
			ViewData.Model = item;

			return View();
		}


		[HttpGet]
		[UrlSubAction("IndexAdmin")]
		[UrlRoute("Products/Add", Path = "Admin/Catalog")]
		[LoadModelState]
		[ReturnUrlEnable]
		[CmsAuthorize(CatalogModule.ProductsRole)]
		public ActionResult Add(int? categoryId)
		{
			return Edit(null, categoryId);
		}

		[HttpPost]
		[ValidateInput(false)]
		public ActionResult Add(HttpPostedFileBase[] newImages, string returnUrl)
		{
			return Edit(null, newImages, returnUrl);
		}

		[HttpGet]
		[UrlSubAction("IndexAdmin")]
		[UrlRoute("Products/{id}/Edit", Path = "Admin/Catalog")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		[CmsAuthorize(CatalogModule.ProductsRole)]
		[LoadModelState]
		[ReturnUrlEnable]
		public ActionResult Edit(int? id, int? categoryId)
		{
			if (id.HasValue)
			{
				Product item = Data.Get<Product>(id.Value);
				if (item == null)
					return HttpNotFound();

				ViewData.Model = item;
				categoryId = item.Category.Id;
			}

			//dropdown list with categories
			List<Category> all = Data.Query<Category>().ToList();
			ViewBag.Categories = GenerateCategoriesTree(all.Where(r => r.Parent == null), null);
			ViewData["Folder"] = CreateFolder();
			if (ViewData["Category.Id"] == null)
				ViewData["Category.Id"] = categoryId;
			else
				categoryId = ViewData["Category.Id"].To<int?>();

			if (categoryId.HasValue)
				ViewData["Category"] = Data.Get<Category>(categoryId.Value);

			return View("Edit");
		}


		[HttpPost]
		[CmsAuthorize(CatalogModule.ProductsRole)]
		[ValidateInput(false)]
		public ActionResult Edit(int? id, HttpPostedFileBase[] newImages, string returnUrl)
		{
			// get product
			Product p;
			if (id.HasValue)
			{
				p = Data.Get<Product>(id.Value);
				if (p == null)
					return HttpNotFound();
			}
			else
				p = new Product();

			// bind
			TryUpdateModel(p);

			// is model valid?
			if (ModelState.IsValid)
			{
				using (ITransaction t = Data.BeginTransaction())
				{
					// typograf textFull
					if (!String.IsNullOrEmpty(p.TextFull))
						p.TextFull = Typograf.ProcessText(p.TextFull, TypografEntityTypes.HtmlEntities);

					// typograf textShort
					if (!String.IsNullOrEmpty(p.TextShort))
						p.TextShort = Typograf.ProcessText(p.TextShort, TypografEntityTypes.HtmlEntities);

					// images edit
					for (int i = p.Pictures.Count - 1; i >= 0; i--)
					{
						string key = "ImageSort" + p.Pictures[i].Id;
						int sort = ValueProvider.GetValue<int>(key, -1);
						if (sort == -1)
							p.Pictures.RemoveAt(i);
						else
							p.Pictures[i].Sort = sort;
					}

					// image add
					if (newImages != null)
					{
						foreach (HttpPostedFileBase file in newImages)
						{
							if (file != null)
							{
								byte[] bytes = file.GetBytes();
								if (bytes != null)
								{
									ProductImage pi = new ProductImage(p, bytes);
									pi.Sort = p.Pictures.Count() > 0 ? p.Pictures.Max(r => r.Sort) + 1 : 0;
									p.Pictures.Add(pi);
								}
							}
						}
					}

					// submit changes to db. Ta-daaaam!
					Data.SaveOrUpdate(p);
					t.Commit();
				}

				if (id == null)
					return RedirectToAction("Item", new { id = p.Id });
				return RedirectToReturnUrl(Url.Action("Item", new { id = p.Id }));
			}

			this.SaveModelState();
			return RedirectToAction(id == null ? "Add" : "Edit", new { returnUrl });
		}

		/// <summary>
		/// Delete product item.
		/// </summary>
		/// <param name="id">News item id.</param>
		[HttpGet]
		[UrlSubAction("IndexAdmin")]
		[UrlRoute("Products/{id}/Delete", Path = "Admin/Catalog")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		[CmsAuthorize(CatalogModule.ProductsRole)]
		[ReturnUrlEnable]
		public ActionResult Delete(int id)
		{
			// get product
			Product model = Data.Get<Product>(id);
			if (model == null)
				return HttpNotFound();

			// delete
			using (ITransaction t = Data.BeginTransaction())
			{
				Data.Delete(model);
				t.Commit();
			}

			// return
			return RedirectToReturnUrl(Url.Action("IndexAdmin"));
		}

	}
}